﻿#include "Network.h"
#include "../Settings.h"
#include "../Forms/Client.CMain.h"
#include "../../Shared/ServerPackets.h"
#include "../MirControls/MirScene.h"
#include "../MirControls/MirMessageBox.h"
#include "../../Shared/ClientPackets.h"

using namespace Client::MirControls;
namespace C = ClientPackets;

namespace Client::MirNetwork
{

TcpClient *Network::_client;
int Network::ConnectAttempt = 0;
bool Network::Connected = false;
long long Network::TimeOutTime = 0;
long long Network::TimeConnected = 0;
ConcurrentQueue<Packet*> *Network::_receiveList;
ConcurrentQueue<Packet*> *Network::_sendList;
std::vector<unsigned char> Network::_rawData(0);
const std::vector<unsigned char> Network::_rawBytes = std::vector<unsigned char>(8 * 1024);

    void Network::Connect()
    {
        if (_client != nullptr)
        {
            Disconnect();
        }

        ConnectAttempt++;

        _client = new TcpClient();
        _client->NoDelay = true;
        _client->BeginConnect(Settings::IPAddress, Settings::Port, [&] (System::IAsyncResult ar) {Connection(ar);}, std::any());

    }

    void Network::Connection(IAsyncResult *result)
    {
        try
        {
            _client->EndConnect(result);

            if (!_client->Connected)
            {
                Connect();
                return;
            }

            _receiveList = new ConcurrentQueue<Packet*>();
            _sendList = new ConcurrentQueue<Packet*>();
            _rawData = std::vector<unsigned char>(0);

            TimeOutTime = CMain::Time + Settings::TimeOut;
            TimeConnected = CMain::Time;


            BeginReceive();
        }
        catch (const SocketException &e1)
        {
            Connect();
        }
        catch (const std::runtime_error &ex)
        {
            if (Settings::LogErrors)
            {
                CMain::SaveError(ex.what());
            }
            Disconnect();
        }
    }

    void Network::BeginReceive()
    {
        if (_client == nullptr || !_client->Connected)
        {
            return;
        }

        try
        {
            _client->Client->BeginReceive(_rawBytes, 0, _rawBytes.size(), SocketFlags::None, [&] (System::IAsyncResult ar) {ReceiveData(ar);}, _rawBytes);
        }
        catch (...)
        {
            Disconnect();
        }
    }

    void Network::ReceiveData(IAsyncResult *result)
    {
        if (_client == nullptr || !_client->Connected)
        {
            return;
        }

        int dataRead;

        try
        {
            dataRead = _client->Client->EndReceive(result);
        }
        catch (...)
        {
            Disconnect();
            return;
        }

        if (dataRead == 0)
        {
            Disconnect();
        }

        std::vector<unsigned char> rawBytes = dynamic_cast<std::vector<unsigned char>>(result->AsyncState);

        std::vector<unsigned char> temp = _rawData;
        _rawData = std::vector<unsigned char>(dataRead + temp.size());
        Buffer::BlockCopy(temp, 0, _rawData, 0, temp.size());
        Buffer::BlockCopy(rawBytes, 0, _rawData, temp.size(), dataRead);

        Packet *p;
        std::vector<unsigned char> data;

        while ((p = Packet::ReceivePacket(_rawData, _rawData)) != nullptr)
        {
            _receiveList->Enqueue(p);
            data.AddRange(p->GetPacketBytes());
        }

        CMain::BytesReceived += data.size();

        BeginReceive();
    }

    void Network::BeginSend(std::vector<unsigned char> &data)
    {
        if (_client == nullptr || !_client->Connected || data.empty())
        {
            return;
        }

        try
        {
            _client->Client->BeginSend(data.ToArray(), 0, data.size(), SocketFlags::None, [&] (System::IAsyncResult ar) {SendData(ar);}, std::any());
        }
        catch (...)
        {
            Disconnect();
        }
    }

    void Network::SendData(IAsyncResult *result)
    {
        try
        {
            _client->Client->EndSend(result);
        }
        catch (...)
        {
        }
    }

    void Network::Disconnect()
    {
        if (_client == nullptr)
        {
            return;
        }

        _client->Close();

        TimeConnected = 0;
        Connected = false;
        _sendList = nullptr;
        _client = nullptr;

        _receiveList = nullptr;
    }

    void Network::Process()
    {
        if (_client == nullptr || !_client->Connected)
        {
            if (Connected)
            {
                while (_receiveList != nullptr && !_receiveList->IsEmpty)
                {
                    Packet p;
                    if (!_receiveList->TryDequeue(p) || p == nullptr)
                    {
                        continue;
                    }
                    if (!(dynamic_cast<ServerPackets::Disconnect*>(p) != nullptr) && !(dynamic_cast<ServerPackets::ClientVersion*>(p) != nullptr))
                    {
                        continue;
                    }

                    MirScene::ActiveScene->ProcessPacket(p);
                    _receiveList = nullptr;
                    return;
                }

                MirMessageBox::Show("Lost connection with the server.", true);
                Disconnect();
                return;
            }
            return;
        }

        if (!Connected && TimeConnected > 0 && CMain::Time > TimeConnected + 5000)
        {
            Disconnect();
            Connect();
            return;
        }



        while (_receiveList != nullptr && !_receiveList->IsEmpty)
        {
            Packet p;
            if (!_receiveList->TryDequeue(p) || p == nullptr)
            {
                continue;
            }
            MirScene::ActiveScene->ProcessPacket(p);
        }


        if (CMain::Time > TimeOutTime && _sendList != nullptr && _sendList->IsEmpty)
        {
            C::KeepAlive tempVar();
            _sendList->Enqueue(&tempVar);
        }

        if (_sendList == nullptr || _sendList->IsEmpty)
        {
            return;
        }

        TimeOutTime = CMain::Time + Settings::TimeOut;

        std::vector<unsigned char> data;
        while (!_sendList->IsEmpty)
        {
            Packet p;
            if (!_sendList->TryDequeue(p))
            {
                continue;
            }
            data.AddRange(p::GetPacketBytes());
        }

        CMain::BytesSent += data.size();

        BeginSend(data);
    }

    void Network::Enqueue(Packet *p)
    {
        if (_sendList != nullptr && p != nullptr)
        {
            _sendList->Enqueue(p);
        }
    }
}
